﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Missbot.Common;
using Dolany.UtilityTool;
using Missbot.Core.Common;
using Missbot.Model.MsgInfos;

namespace Missbot.Core.Base
{
    /// <summary>
    /// 消息通道基类
    /// </summary>
    public abstract class IMsgChannel : ISingleton
    {
        public abstract string Name { get; }
        public abstract ChannelType BindingType { get; }
        public abstract string ChannelFlag { get; }

        private readonly object _HookLock = new();
        private readonly List<WaiterUnit> UnitsList = new();

        protected abstract void Send(OutboxDTO dto);

        /// <summary>
        /// 按session原路回信
        /// </summary>
        /// <param name="session"></param>
        /// <param name="text"></param>
        /// <param name="file"></param>
        public void Send(InboxSession session, string text, string file = null)
        {
            var outDto = new OutboxDTO
            {
                User = session.User,
                Group = session.Group,
                Text = text,
                FileIDs = string.IsNullOrEmpty(file) ? null : new[] { file },
                SourceID = session.SourceID
            };
            Send(outDto);
            OutboxRecDA.Record(outDto, Name);
        }

        /// <summary>
        /// 发送群组消息
        /// </summary>
        /// <param name="Users"></param>
        /// <param name="Group"></param>
        /// <param name="SourceID"></param>
        /// <param name="text"></param>
        /// <param name="file"></param>
        public virtual void SendGroup(IEnumerable<InstanceUser> Users, InstanceGroup Group, string SourceID, string text, string file = null)
        {
            foreach (var user in Users)
            {
                Send(new InboxSession { User = user, Group = Group, SourceID = SourceID }, text, file);
            }
        }

        /// <summary>
        /// 解析消息
        /// </summary>
        /// <param name="msg"></param>
        /// <returns></returns>
        public InboxSession ParseData(string msg)
        {
            var session = ParseDataImpl(msg);
            if (session == null)
            {
                return null;
            }

            session.Channel = this;
            return IsWaitedMsg(session) ? null : session;
        }

        /// <summary>
        /// 是否是被等待的消息
        /// </summary>
        /// <param name="session"></param>
        /// <returns></returns>
        private bool IsWaitedMsg(InboxSession session)
        {
            var waitUnit = GetWaiter(session);
            if (waitUnit == null)
            {
                return false;
            }

            waitUnit.ResultInfos.Add(session);
            waitUnit.Signal.Set();
            return true;
        }

        private WaiterUnit GetWaiter(InboxSession session)
        {
            lock (_HookLock)
            {
                if (UnitsList.IsNullOrEmpty())
                {
                    return null;
                }

                foreach (var wu in UnitsList.Where(ul => ul.JudgePredicate != null))
                {
                    if (wu.JudgePredicate(session))
                    {
                        return wu;
                    }
                }

                foreach (var wu in UnitsList.Where(ul => !ul.JudgePredicateGroup.IsNullOrEmpty()))
                {
                    var pre = wu.JudgePredicateGroup.FirstOrDefault(p => p(session));
                    if (pre == null)
                    {
                        continue;
                    }

                    wu.JudgePredicateGroup.Remove(pre);
                    wu.ResultInfos.Add(session);
                    return wu.JudgePredicateGroup.IsNullOrEmpty() ? wu : null;
                }
            }

            return null;
        }

        /// <summary>
        /// 按规则等待消息
        /// </summary>
        /// <param name="judgeFunc"></param>
        /// <param name="timeout"></param>
        /// <returns></returns>
        public InboxSession Wait(Predicate<InboxSession> judgeFunc, int timeout = 12)
        {
            var signal = new AutoResetEvent(false);
            var unit = new WaiterUnit { JudgePredicate = judgeFunc, Signal = signal };

            lock (_HookLock)
            {
                UnitsList.Add(unit);
            }

            signal.WaitOne(timeout * 1000);

            lock (_HookLock)
            {
                unit = UnitsList.First(u => u.Id == unit.Id);
                UnitsList.Remove(unit);
            }

            var res = unit.ResultInfos.FirstOrDefault();
            return res;
        }

        public List<InboxSession> Wait(List<Predicate<InboxSession>> judgeFuncGroup, int timeout = 12)
        {
            var signal = new AutoResetEvent(false);
            var unit = new WaiterUnit { JudgePredicateGroup = judgeFuncGroup, Signal = signal };

            lock (_HookLock)
            {
                UnitsList.Add(unit);
            }

            signal.WaitOne(timeout * 1000);

            lock (_HookLock)
            {
                unit = UnitsList.First(u => u.Id == unit.Id);
                UnitsList.Remove(unit);
            }

            return unit.ResultInfos;
        }

        protected abstract InboxSession ParseDataImpl(string msg);
    }

    /// <summary>
    /// 等待单元
    /// </summary>
    public class WaiterUnit
    {
        /// <summary>
        /// 等待单元ID
        /// </summary>
        public string Id { get; } = Guid.NewGuid().ToString();

        /// <summary>
        /// 判断表达式
        /// </summary>
        public Predicate<InboxSession> JudgePredicate { get; init; }

        public List<Predicate<InboxSession>> JudgePredicateGroup { get; init; }

        /// <summary>
        /// 阻塞信号量
        /// </summary>
        public AutoResetEvent Signal { get; init; }

        /// <summary>
        /// 等待结果集
        /// </summary>
        public List<InboxSession> ResultInfos { get; } = new();
    }
}